package org.mmtk.plan.scale;

import org.mmtk.plan.Phase;
import org.mmtk.plan.Simple;
import org.mmtk.plan.StopTheWorld;
import org.mmtk.plan.TransitiveClosure;
import org.mmtk.plan.scale.colors.CMSweeper;
import org.mmtk.plan.scale.utils.ScaleDebugOption;
import org.mmtk.policy.Space;
import org.mmtk.policy.SegregatedFreeListSpace.Scanner;
import org.mmtk.utility.Log;
import org.mmtk.utility.heap.VMRequest;
import org.mmtk.utility.options.Options;
import org.mmtk.utility.statistics.Stats;
import org.mmtk.vm.Lock;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.ObjectReference;

@Uninterruptible
public class Scale extends StopTheWorld {
	private static final boolean DEBUG = false;
	/****************************************************************************
	 * Constants
	 */
	//this is to allow running GC at smaller stress intervals
	private static final int MY_DEFAULT_POLL_FREQUENCY = (128 << 8) >> LOG_BYTES_IN_PAGE;
		
	//these are to set different flavors of the closure process
	static final int METRIX_TRACE_FLAVOR = 1;				
	static final int METRIX_TRACE_METRIX_FLAVOR = 2;	
	
	/**
	 * Every closure must be prepared
	 * Different closures differently
	 * What matters is whether the closure is the first, is the last, is concurrent or is
	 * single threaded
	 */
	public static final int CLOSURE_FIRST_CONCURRENT = 1;
	public static final int CLOSURE_LAST_CONCURRENT = 2;
	public static final int CLOSURE_FIRST_SINGLE = 3;
	public static final int CLOSURE_LAST_SINGLE = 4;
	public static final int CLOSURE_CONCURRENT = 5;
	public static final int CLOSURE_SINGLE = 6;
	public static int closure;
	private int scan;
	
	public static final short depthClosure = Phase.createSimple("dCl");
	public static final short fixClosure = Phase.createSimple("fCl");
	public static final short regClosure = Phase.createSimple("rCl");
	public static final short depthCheckClosure = Phase.createSimple("dcCl");
	public static final short prepareClosure = Phase.createSimple("prepCl", null);
	
	private static final short drClosure = Phase.createComplex(
			"drClosure", 
			null, 
			Phase.scheduleMutator(PREPARE), 
			Phase.scheduleGlobal(PREPARE), 
			Phase.scheduleCollector(PREPARE),
			Phase.scheduleComplex(prepareStacks), 
			Phase.scheduleCollector(STACK_ROOTS), 
			Phase.scheduleCollector(ROOTS), 
			Phase.scheduleGlobal(ROOTS),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(depthClosure),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE),
			Phase.scheduleCollector(regClosure));
	
	private static final short drdClosure = Phase.createComplex(
			"drdClosure", 
			null, 
			Phase.scheduleMutator(PREPARE), 
			Phase.scheduleGlobal(PREPARE), 
			Phase.scheduleCollector(PREPARE),
			Phase.scheduleComplex(prepareStacks), 
			Phase.scheduleCollector(STACK_ROOTS), 
			Phase.scheduleCollector(ROOTS), 
			Phase.scheduleGlobal(ROOTS),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(depthClosure),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE),
			Phase.scheduleCollector(regClosure),
			Phase.scheduleGlobal(prepareClosure),
			Phase.scheduleCollector(prepareClosure),
			Phase.scheduleGlobal(CLOSURE), 
			Phase.scheduleCollector(depthCheckClosure));
	
	/****************************************************************************
	 * Class variables
	 */
	public static final ScaleMarkSweepSpace scaleSpace = new ScaleMarkSweepSpace("scaleMS",
			MY_DEFAULT_POLL_FREQUENCY, VMRequest.create());
	public static final int SCALE_MS_SPACE = scaleSpace.getDescriptor();
	
	static final int SCAN_MARK = 0;
	public static final int PRIVATE_SCAN_MARK = 1;
	protected static boolean inCheckClosure;
	public static boolean isInCheckClosure() {
		return inCheckClosure;
	}
	public static void inCheckClosure() {
		inCheckClosure = true;
	}
	protected static int collectorThreads;
	protected static int traceFlavor;
	public static int getTraceFlavor() {
		return traceFlavor;
	}
	
	/****************************************************************************
	 * Instance variables
	 */
	protected Scanner scanner;
	protected CMSweeper sweeper;
	public final ScaleTrace scaleTrace = new ScaleTrace(metaDataSpace);
	private static boolean inRelease;
	static boolean inRelease() {
		return inRelease;
	}
	private static long initialValues;
	private static long initialMSValues;
	private static Lock collectorsLock = VM.newLock("CollectorsLock");
	//caller must hold the collectorsLock
	static void addInitialValues(long values, long msValues) {
		collectorsLock.acquire();
		if(DEBUG && Options.scaleDebug.getValue() > 5) {
			Log.prependThreadId();
			Log.write("\tScale.addInitialValues - initialValues=");
			Log.write(initialValues);
			Log.write(", initialMSValues=");
			Log.write(initialMSValues);
			Log.write(", values=");
			Log.write(values);
			Log.write(", msValues=");
			Log.writeln(msValues);
			Log.flush();
		}
		initialValues += values;
		initialMSValues += msValues;	
		collectorsLock.release();
	}
	static long getInitialValues() {
		return initialValues;
	}
	static long getInitialMSValues() {
		return initialMSValues;
	}
	static void acquireCollectorsLock() {
		collectorsLock.acquire();
		if(DEBUG && Options.scaleDebug.getValue() > 7) {
			Log.prependThreadId();
			Log.writeln("Scale.acquireCollectorsLock");
			Log.flush();
		}
	}
	static void releaseCollectorsLock() {		
		if(DEBUG && Options.scaleDebug.getValue() > 7) {
			Log.prependThreadId();
			Log.writeln("Scale.releaseCollectorsLock");
			Log.flush();
		}
		collectorsLock.release();
	}
	public Scale() {
		//disable reference types scan
		Options.noReferenceTypes.setValue(true);
	    Options.noFinalizer.setValue(true);	    
	    Options.scaleDebug = new ScaleDebugOption();	   
	    //scanner = new ScaleScanner();
	    scanner = null;
	    sweeper = null;
	    scan = 0;
	}
	
	/*****************************************************************************
	 * 
	 * Collection
	 */

	/**
	 * Perform a (global) collection phase.
	 * 
	 * @param phaseId
	 *            Collection phase to execute.
	 */
	@Inline
	@Override
	public void collectionPhase(short phaseId) {
		if (DEBUG && Options.scaleDebug.getValue() > 7) {
			Log.write("Scale.collectionPhase - entered with phaseId=");Log.write(phaseId);
			Log.write(", phaseName=");Log.writeln(Phase.getName(phaseId));
			Log.flush();
		}
		
		if (phaseId == PREPARE) {
			//disable reference types scan
			VM.finalizableProcessor.clear();
		    VM.weakReferences.clear();
		    VM.softReferences.clear();
		    VM.phantomReferences.clear();
		    
			super.collectionPhase(phaseId);
			scaleTrace.prepare();
			scaleSpace.prepare(true, true);
			inCheckClosure = false;
			
			//set first closure type
			doSetFirstClosureType();

			//before cycle starts
			scan = 1;
			scaleSpace.scan(scanner, scan);
			return;
		}

		if (phaseId == prepareClosure) {
			initialValues = 0;
			initialMSValues = 0;
			scaleTrace.prepare();
			if(!isFirstClosure()) {
				//this will be not a first closure
			    nonMovingSpace.prepare(true);
			    if (USE_CODE_SPACE) {
			        smallCodeSpace.prepare(true);
			    }
			    immortalSpace.prepare();
			    VM.memory.globalPrepareVMSpace();
			    scaleSpace.prepare(true, false);
			    //before additional closure starts
			    scan++;
			    scaleSpace.scan(scanner, scan);
			}
			
			return;
		}
		
		if (phaseId == CLOSURE) {
			this.scaleTrace.prepare();
			
			if(isLastClosure()) {
				return;
			}
					
			if(DEBUG && Options.scaleDebug.getValue() > 5) {
				Log.write("Scale.collectionPhase - CLOSURE, before doSetNextClosureType closure=");
				Log.writeln(closure);
			}
			
			doSetNextClosureType();
			
			if(DEBUG && Options.scaleDebug.getValue() > 5) {
				Log.write("Scale.collectionPhase - CLOSURE, after doSetNextClosureType closure=");
				Log.writeln(closure);
			}
			
			return;
		}
		
		if (phaseId == RELEASE) {
			inRelease = true;
			//before releasing
			scan++;
		    scaleSpace.scan(scanner,scan);
		    
			scaleTrace.release();
			
			if (DEBUG && Options.scaleDebug.getValue() > 3) {
				Log.writeln("Scale.collectionPhase - RELEASE before scaleSpace.release(sweeper)");
				Log.flush();
			}
			scaleSpace.release(sweeper);
			
			super.collectionPhase(phaseId);
			
			if (DEBUG && Options.scaleDebug.getValue() > 3) {
				Log.writeln("Scale.collectionPhase - RELEASE done");
				Log.flush();
			}
			inRelease = false;
			return;
		}
		
	    if (phaseId == Simple.COMPLETE) {
	    	super.collectionPhase(phaseId);
	    	if(scanner != null) {
	    		scanner.release();
	    	}
	        return;
	    }

		super.collectionPhase(phaseId);
	}

	/**
	 * First closure is depth measuring in single thread
	 */
	protected void doSetFirstClosureType() {	
		closure = CLOSURE_FIRST_SINGLE; 
	}
		
	protected void doSetNextClosureType() {
		if (traceFlavor == METRIX_TRACE_FLAVOR) {
			if (VM.VERIFY_ASSERTIONS) {
				VM.assertions._assert(closure == CLOSURE_FIRST_SINGLE,
				"Scale.doSetNextClosureType encountered wrong trace type");
			}
			closure = CLOSURE_LAST_SINGLE;
		} else if (traceFlavor == METRIX_TRACE_METRIX_FLAVOR) {
			if (VM.VERIFY_ASSERTIONS) {
				VM.assertions._assert(closure == CLOSURE_FIRST_SINGLE || closure == CLOSURE_CONCURRENT,
				"Scale.doSetNextClosureType encountered wrong trace type");
			}
			if(closure == CLOSURE_FIRST_SINGLE) {
				closure = CLOSURE_CONCURRENT;
			} else if(closure == CLOSURE_CONCURRENT) {
				closure = CLOSURE_LAST_SINGLE;
			}
		} else if (VM.VERIFY_ASSERTIONS) {
			VM.assertions._assert(false,
			"Scale.doSetNextClosureType encountered wrong trace flavor");
		}
	}
	
	protected short getReplacementPhaseID() {
		if(traceFlavor == METRIX_TRACE_FLAVOR) {
			return drClosure;
		} else if(traceFlavor == METRIX_TRACE_METRIX_FLAVOR) {
			return drdClosure;
		}
		
		if (VM.VERIFY_ASSERTIONS) {
			VM.assertions._assert(false,"Scale.getReplacementPhaseID encountered wrong trace flavor");
		}
		
		return 0;
	}
	
	public static boolean isLastClosure() {
		if(closure == CLOSURE_LAST_SINGLE ||
				closure == CLOSURE_LAST_CONCURRENT) {
			return true;
		}
		return false;
	}
	
	public static boolean isFirstClosure() {
		if(closure == CLOSURE_FIRST_SINGLE ||
				closure == CLOSURE_FIRST_CONCURRENT) {
			return true;
		}
		return false;
	}
	
	public static boolean isConcurrentClosure() {
		if(closure == CLOSURE_FIRST_CONCURRENT ||
				closure == CLOSURE_LAST_CONCURRENT ||
				closure == CLOSURE_CONCURRENT) {
			return true;
		}
		return false;
	}
	/*****************************************************************************
	 * 
	 * Accounting
	 */

	/**
	 * Return the number of pages reserved for use given the pending allocation.
	 * The superclass accounts for its spaces, we just augment this with the
	 * mark-sweep space's contribution.
	 * 
	 * @return The number of pages reserved given the pending allocation,
	 *         excluding space reserved for copying.
	 */
	public int getPagesUsed() {
		return (scaleSpace.reservedPages() + super.getPagesUsed());
	}

	/**
	 * Calculate the number of pages a collection is required to free to satisfy
	 * outstanding allocation requests.
	 * 
	 * @return the number of pages a collection is required to free to satisfy
	 *         outstanding allocation requests.
	 */
	public int getPagesRequired() {
		return super.getPagesRequired() + scaleSpace.requiredPages();
	}

	/**
	 * @see org.mmtk.plan.Plan#willNeverMove
	 * 
	 * @param object
	 *            Object in question
	 * @return True if the object will never move
	 */
	@Override
	public boolean willNeverMove(ObjectReference object) {
		if (Space.isInSpace(SCALE_MS_SPACE, object)) {
			return true;
		}
		return super.willNeverMove(object);
	}

	/**
	 * Register specialized methods.
	 */
	@Interruptible
	@Override
	protected void registerSpecializedMethods() {
		TransitiveClosure.registerSpecializedScan(SCAN_MARK, ScaleTraceLocal.class);
		TransitiveClosure.registerSpecializedScan(PRIVATE_SCAN_MARK, ScaleTraceLocal.class);
		super.registerSpecializedMethods();
	}

	/**
	 * Print something just before VM exit
	 */
	@Override
	@Interruptible
	public final void notifyExit(int value) {
		if(DEBUG || Options.scaleDebug.getValue() > 2) {
			Log.writeln("======================");
			super.notifyExit(value);
			Log.write(" === EXITING run in total ");
			totalTime.printTotalMillis();
			Log.writeln(" ms");
			Log.flush();
		}
	}

	/**
	 * Print something before the cycle
	 */
	@Override
	public final void printPreStats() {		
		if(DEBUG || Options.scaleDebug.getValue() > 2) {
			Log.writeln("====== BEFORE GC =====");
			super.printPreStats();
			if(DEBUG && Options.scaleDebug.getValue() > 5) {
				Space.printVMMap();
			}
			Log.writeln("======================");
		}
		if(Stats.gcCount() > 4) {
			//VM.objectModel.setDebug(true);
		}
		Log.flush();
	}
	
	/**
	 * Print something after the cycle
	 */
	@Override
	public final void printPostStats() {
		if(DEBUG || Options.scaleDebug.getValue() > 2) {
			Log.writeln("====== AFTER GC ======");
			super.printPostStats();
			if(DEBUG && Options.scaleDebug.getValue() > 5) {
				Space.printVMMap();
			}
			Log.writeln("======================");
		}
		Log.flush();
	}

	/**
	 * Print something after VM boots
	 */
	@Override
	@Interruptible
	public final void postBoot() {
		super.postBoot();
		
		if(DEBUG && Options.scaleDebug.getValue() > 5) {
			VM.config.printConfig();
			Space.printVMMap();
			Log.writeln("======================");
		}
	    
	    traceFlavor = METRIX_TRACE_METRIX_FLAVOR;
		//traceFlavor = METRIX_TRACE_FLAVOR;
		
		initializeRun();	   
	    	        
		if(DEBUG && Options.scaleDebug.getValue() > 3) {
		    Log.writeln(" ===== BOOTED ===== ");
			Log.flush();
		}
	}
	
	@Interruptible
	protected void initializeRun() {	
		if(DEBUG || Options.scaleDebug.getValue() > 3) {
			Log.write("Scale.initializeRun, Running flavor: ");
			Log.writeln(traceFlavor);
			Log.flush();
		}
		
		short replacementPhaseID = getReplacementPhaseID();
		if(replacementPhaseID != -1) {
			if(DEBUG || Options.scaleDebug.getValue() > 3) {
				Log.write("Scale.initializeRun, Replacing the rootClosurePhase with ");
				Log.write(Phase.getName(replacementPhaseID));
				Log.writeln("");
				Log.flush();
			}
			replacePhase(Phase.scheduleComplex(rootClosurePhase), Phase.scheduleComplex(replacementPhaseID));
		}
			
		if(DEBUG && Options.scaleDebug.getValue() > 3) {
			Log.write("Scale.initializeRun, Options.colorMarkSweepMarkBits.getValue()=");Log.writeln(Options.colorMarkSweepMarkBits.getValue());
			Log.write("Scale.initializeRun, UNLOGGED_BIT: ");Log.writeln(ScaleMarkSweepSpace.UNLOGGED_BIT);
			Log.write("Scale.initializeRun, MARK_COUNT_INCREMENT: ");Log.writeln(ScaleMarkSweepSpace.MARK_COUNT_INCREMENT);
			Log.write("Scale.initializeRun, MARK_COUNT_MASK: ");Log.writeln(ScaleMarkSweepSpace.MARK_COUNT_MASK);
			Log.write("Scale.initializeRun, MARK_BITS_MASK: ");Log.writeln(ScaleMarkSweepSpace.MARK_BITS_MASK);
			Log.write("Scale.initializeRun, getMarkState(): ");Log.writeln(scaleSpace.getMarkState());
			Log.write("Scale.initializeRun, getAllocState(): ");Log.writeln(scaleSpace.getAllocState());
		}
		
		if(DEBUG && Options.scaleDebug.getValue() > 5) {
			Log.write("Scale.initializeRun, Closure types:\n\tCLOSURE_FIRST_CONCURRENT=");Log.writeln(CLOSURE_FIRST_CONCURRENT);
			Log.write("\tCLOSURE_LAST_CONCURRENT=");Log.writeln(CLOSURE_LAST_CONCURRENT);
			Log.write("\tCLOSURE_FIRST_SINGLE=");Log.writeln(CLOSURE_FIRST_SINGLE);
			Log.write("\tCLOSURE_LAST_SINGLE=");Log.writeln(CLOSURE_LAST_SINGLE);
			Log.write("\tCLOSURE_CONCURRENT=");Log.writeln(CLOSURE_CONCURRENT);
			Log.write("\tCLOSURE_SINGLE=");Log.writeln(CLOSURE_SINGLE);
			scaleSpace.printSizeClasses();
			Log.flush();
		}
		
		collectorThreads = VM.activePlan.collectorCount();
		
		if(DEBUG && Options.scaleDebug.getValue() > 3) {
			Log.write("Scale.initializeRun, Number of the collector threads: ");Log.writeln(collectorThreads);
			Log.flush();
		}
	}
}
